import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC

# 读取数据（尝试两种相对路径）
try:
    raw = np.genfromtxt('LearningData.csv', delimiter=',', skip_header=1, dtype=str, encoding='utf-8')
except:
    raw = np.genfromtxt('Works/第7章支持向量机/LearningData.csv', delimiter=',', skip_header=1, dtype=str, encoding='utf-8')

data = np.atleast_2d(raw) # 确保为二维数组

# 提取 X,y（前两列为特征，第三列为标签）
X = data[:, :2]
y_raw = data[:, -1]

# 将标签转换为数值 ±1
try:
    y = y_raw.astype(float)
except:
    # 若是字符串 '1'/'-1' 或 'Yes'/'No' 等，尝试映射
    uniq = np.unique(y_raw)
    mapping = {}
    if set(uniq) <= {'1', '-1'}:
        mapping = {k: float(k) for k in uniq}
    else:
        # 映射第一个为 +1，第二个为 -1（仅在两类且未知名时）
        if uniq.size == 2:
            mapping = {uniq[0]: 1.0, uniq[1]: -1.0}
        else:
            raise ValueError("标签列含超过2类或无法识别，请检查数据。")
    y = np.array([mapping[v] for v in y_raw], dtype=float)

# 训练线性 SVM（C 大以近似硬间隔）
clf = SVC(kernel='linear', C=1e6)
clf.fit(X.astype(float), y)

w = clf.coef_[0]
b = clf.intercept_[0]
sv = clf.support_vectors_

print("w:", w)
print("b:", b)
print("support vectors:\n", sv)
print(f"超平面表达式: {w[0]:.4f} * x1 + {w[1]:.4f} * x2 + {b:.4f} = 0")

# 绘图：散点 + 决策边界 + margins + 支持向量
plt.figure(figsize=(8,6))
# 数据点
pos = y > 0
neg = y < 0
plt.scatter(X[pos,0].astype(float), X[pos,1].astype(float), c='b', marker='o', label='+1')
plt.scatter(X[neg,0].astype(float), X[neg,1].astype(float), c='r', marker='s', label='-1')

# 网格用于绘制等高线
x_min, x_max = X[:,0].astype(float).min()-1, X[:,0].astype(float).max()+1
y_min, y_max = X[:,1].astype(float).min()-1, X[:,1].astype(float).max()+1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 500), np.linspace(y_min, y_max, 500))
grid = np.c_[xx.ravel(), yy.ravel()]
Z = clf.decision_function(grid).reshape(xx.shape)

# 绘制超平面和 margin（levels 0, ±1）
contour = plt.contour(xx, yy, Z, levels=[-1, 0, 1], linestyles=['--','-','--'], colors=['gray','k','gray'])
plt.clabel(contour, fmt={-1:'margin -1', 0:'decision boundary', 1:'margin +1'}, inline=True, fontsize=8)

# 标注支持向量
plt.scatter(sv[:,0].astype(float), sv[:,1].astype(float), s=120, facecolors='none', edgecolors='k', linewidths=1.5, label='support vectors')

plt.legend()
plt.xlabel('x1')
plt.ylabel('x2')
plt.title('Linear SVM — decision boundary, margins, support vectors')
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.grid(True)
plt.show()